#include "Gk.h"

#ifdef GK_DEVICE_DIRECTX11

#include "GkDevice.DirectX11.h"
#include "..\GkContext.GenericDeferred.h"
#include <d3dx11.h>

#include <memory.h>

#pragma comment( lib, "d3d11.lib" )
#pragma comment( lib, "d3dx11.lib" )

namespace DX11Helpers
{
	struct DX11ShaderHeader
	{
		cr::uint32_t	m_codeSize;
		cr::uint32_t	m_numConstants;
	};
}

namespace gk
{
	DeviceHandle CreateDevice()
	{
		return new DeviceDirectX11;
	}

	void DeviceDirectX11::Initialise(InitParamsHandle hInitParams)
	{
		DeviceCommon::InitialisePlatform(hInitParams);

		DXGI_SWAP_CHAIN_DESC sd;
		ZeroMemory( &sd, sizeof( sd ) );
		sd.BufferCount = 1;
		sd.BufferDesc.Width = hInitParams->m_displayWidth;
		sd.BufferDesc.Height = hInitParams->m_displayHeight;
		sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		sd.BufferDesc.RefreshRate.Numerator = 60;
		sd.BufferDesc.RefreshRate.Denominator = 1;
		sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		sd.OutputWindow = (HWND)pf::GetPlatform()->GetPlatformWindowHandle();
		sd.SampleDesc.Count = 1;
		sd.SampleDesc.Quality = 0;
		sd.Windowed = TRUE;

		HRESULT				hr				= S_OK;
		D3D_FEATURE_LEVEL	FeatureLevels	= D3D_FEATURE_LEVEL_11_0;
		D3D_FEATURE_LEVEL*	pFeatureLevel	= NULL;
		UINT				flags			= hInitParams->m_enableDebugging ? D3D11_CREATE_DEVICE_DEBUG : 0;

		D3D_DRIVER_TYPE errorDriver = D3D_DRIVER_TYPE_UNKNOWN;
		D3D_DRIVER_TYPE driverType[] = 
		{
			D3D_DRIVER_TYPE_HARDWARE,
			D3D_DRIVER_TYPE_REFERENCE,
			D3D_DRIVER_TYPE_UNKNOWN
		};
		D3D_DRIVER_TYPE* pCurType = driverType;

		while (*pCurType != errorDriver)
		{

			if(SUCCEEDED(hr = D3D11CreateDeviceAndSwapChain(
				NULL,
				*pCurType,
				NULL,
				flags,
				&FeatureLevels,
				1,
				D3D11_SDK_VERSION,
				&sd,
				&m_pSwapChain,
				&m_pDevice,
				pFeatureLevel,
				&m_pDeviceContext
			)))
			{
				break;
			}

			pCurType++;
		}

		if (*pCurType == D3D_DRIVER_TYPE_UNKNOWN)
		{
			CR_ERROR_STRING("Error creating D3D driver");
		}

		ID3D11Texture2D* pBackBuffer = NULL;
		if(FAILED(hr = m_pSwapChain->GetBuffer( 0, __uuidof( *pBackBuffer ), ( LPVOID* )&pBackBuffer )))
		{
			CR_ASSERT_MSG(pBackBuffer != NULL, "m_pSwapChain->GetBuffer failed.");
		}	

		m_pRenderTargetView = NULL;
		if(FAILED(hr = m_pDevice->CreateRenderTargetView( pBackBuffer, NULL, &m_pRenderTargetView )))
		{
			CR_ASSERT_MSG(m_pRenderTargetView != NULL, "m_pDevice->CreateRenderTargetView failed.");
		}
 
 		pBackBuffer->Release();
 
 		m_pDeviceContext->OMSetRenderTargets(1, &m_pRenderTargetView, NULL);
 
		D3D11_VIEWPORT vp;
		vp.Width		= (FLOAT)hInitParams->m_displayWidth;
		vp.Height		= (FLOAT)hInitParams->m_displayHeight;
		vp.MinDepth		= 0.0f;
		vp.MaxDepth		= 1.0f;
		vp.TopLeftX		= 0;
		vp.TopLeftY		= 0;
		m_pDeviceContext->RSSetViewports( 1, &vp );

		cr::ClearObject(GetBackBuffer());
		GetBackBuffer()->m_internal.m_pRenderTargetView = m_pRenderTargetView;
		GetBackBuffer()->m_width = hInitParams->m_displayWidth;
		GetBackBuffer()->m_height = hInitParams->m_displayHeight;
		GetBackBuffer()->m_format = hInitParams->m_displayFormat;
		GetBackBuffer()->m_multisampleType = MultisampleType::None;
		GetBackBuffer()->m_createTexture = false;

		DeviceCommon::InitialiseCommon(hInitParams);

		D3D11_SAMPLER_DESC sampDesc;
		ZeroMemory( &sampDesc, sizeof(sampDesc) );
		sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
		sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
		sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
		sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
		sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
		sampDesc.MinLOD = 0;
		sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
		m_pDevice->CreateSamplerState(&sampDesc, &GetSystemContext()->m_internal.m_pDummySamplerState);
	}

	void DeviceDirectX11::Shutdown()
	{
 		DeviceCommon::ShutdownCommon();
 		m_pRenderTargetView->Release();
		m_pDeviceContext->Release();
		m_pSwapChain->Release();
		m_pDevice->Release();
		DeviceCommon::ShutdownPlatform();
	}

	bool DeviceDirectX11::Present()
	{
		m_pSwapChain->Present(1, 0);
		return pf::GetPlatform()->Present();
	}

	// Object creation
	void DeviceDirectX11::CreateContext(ContextHandle hContext)
	{
		switch (hContext->m_type)
		{
		case ContextType::Immediate:
			{
				hContext->m_internal.m_pContext = m_pDeviceContext;
				break;
			}

		case ContextType::Deferred:
			{
				CR_ERROR_STRING("Deferred contexts are not currently implemented for DX11");
				/*m_pDevice->CreateDeferredContext(0, &newContextDX11.m_pContext);*/
				break;
			}
		}
	}

	void DeviceDirectX11::CreateBuffer(BufferHandle hBuffer)
	{
		D3D11_BUFFER_DESC bufferDesc;
		bufferDesc.ByteWidth = hBuffer->m_stride * hBuffer->m_numElements;
		bufferDesc.Usage = D3D11_USAGE_DEFAULT;
		bufferDesc.BindFlags = TypeConversion::ConvertBindFlag(hBuffer->m_format);
		bufferDesc.CPUAccessFlags = 0;
		bufferDesc.MiscFlags = 0;

		D3D11_SUBRESOURCE_DATA* pInitData = 0;
		D3D11_SUBRESOURCE_DATA initData;

		cr::ClearObject(initData);
		initData.pSysMem = hBuffer->m_pInitData;

		if (initData.pSysMem)
			pInitData = &initData;

		m_pDevice->CreateBuffer(&bufferDesc, pInitData, &hBuffer->m_internal.m_pBuffer);
	}

	void DeviceDirectX11::CreateRenderTarget(RenderTargetHandle hRenderTarget)
	{
		// See if this render target is blank
		bool blank = !hRenderTarget->m_hDepthSurface.IsValid();
		cr::size_t i = 0;
		while (blank && i < Constants::MaxColourTargets)
		{
			if (hRenderTarget->m_hColourSurface[i].IsValid())
			{
				blank = false;
				break;
			}
			++i;
		}

		if (blank)
		{
			// Setup as system render target
			hRenderTarget->m_hColourSurface[0] = GetBackBuffer();
		}
	}

	void DeviceDirectX11::CreateRenderSurface(RenderSurfaceHandle hRenderSurface)
	{
		bool isDepthStencil = false;

		switch (hRenderSurface->m_format)
		{
		case BufferFormat::D16:
		case BufferFormat::D24S8:
			isDepthStencil = true;
			break;
		default:
			break;
		}

		D3D11_TEXTURE2D_DESC desc;
		ZeroMemory(&desc, sizeof(desc));
		desc.Width = (UINT)hRenderSurface->m_width;
		desc.Height = (UINT)hRenderSurface->m_height;
		desc.MipLevels = 1;
		desc.ArraySize = 1;
		desc.Format = TypeConversion::ConvertBufferFormat(hRenderSurface->m_format);
		desc.Usage = D3D11_USAGE_DEFAULT;
		desc.BindFlags = (isDepthStencil ? D3D11_BIND_DEPTH_STENCIL : (D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_RENDER_TARGET));
		desc.SampleDesc.Count = 1;
		desc.SampleDesc.Quality = 0;

		ID3D11Texture2D* pTex;
		m_pDevice->CreateTexture2D(&desc, NULL, &pTex);
		CR_ASSERT(pTex);

		D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
		ZeroMemory(&rtvDesc, sizeof(rtvDesc));

		//ID3D11Resource* pResource = 0;

		switch (hRenderSurface->m_format)
		{
		case BufferFormat::VertexStream:
		case BufferFormat::Index16Stream:
		case BufferFormat::Index32Stream:
			{
				CR_ERROR_STRING("Invalid render-target format");
				break;
			}
		case BufferFormat::D16:
		case BufferFormat::D24S8:
		case BufferFormat::D32:
			{
				m_pDevice->CreateDepthStencilView(pTex, NULL, &hRenderSurface->m_internal.m_pDepthStencilView);
				//hRenderSurface->m_internal.m_pDepthStencilView->GetResource(&hRenderSurface->m_internal.);
				break;
			}
		default:
			{
				// Everything else is a colour buffer
				m_pDevice->CreateRenderTargetView(pTex, NULL, &hRenderSurface->m_internal.m_pRenderTargetView);

				if (hRenderSurface->m_createTexture)
				{
					hRenderSurface->m_surfaceAsTexture.m_width = hRenderSurface->m_width;
					hRenderSurface->m_surfaceAsTexture.m_height = hRenderSurface->m_height;
					hRenderSurface->m_surfaceAsTexture.m_format = hRenderSurface->m_format;
					hRenderSurface->m_surfaceAsTexture.m_numMips = 1;
					hRenderSurface->m_surfaceAsTexture.m_initDataSize = 0;
					hRenderSurface->m_surfaceAsTexture.m_pInitData = 0;
					hRenderSurface->m_internal.m_pRenderTargetView->GetResource(&hRenderSurface->m_surfaceAsTexture.m_internal.m_pResource);
					m_pDevice->CreateShaderResourceView(hRenderSurface->m_surfaceAsTexture.m_internal.m_pResource, NULL, &hRenderSurface->m_surfaceAsTexture.m_internal.m_pShaderResourceView);
				}

				break;
			}
		}

		pTex->Release();
	}

	void DeviceDirectX11::CreateTexture(TextureHandle hTexture)
	{
		cr::ClearObject(hTexture->m_internal);
		D3DX11CreateTextureFromMemory(m_pDevice, hTexture->m_pInitData, hTexture->m_initDataSize, NULL, NULL, &hTexture->m_internal.m_pResource, NULL);
		m_pDevice->CreateShaderResourceView(hTexture->m_internal.m_pResource, NULL, &hTexture->m_internal.m_pShaderResourceView);

		D3D11_RESOURCE_DIMENSION texType;
		hTexture->m_internal.m_pResource->GetType(&texType);

		D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
		hTexture->m_internal.m_pShaderResourceView->GetDesc(&srvDesc);

		switch (srvDesc.ViewDimension)
		{
		case D3D_SRV_DIMENSION_TEXTURE2D:
			{
				ID3D11Texture2D* pTex2D = (ID3D11Texture2D*)hTexture->m_internal.m_pResource;
				D3D11_TEXTURE2D_DESC texDesc;
				pTex2D->GetDesc(&texDesc);
				hTexture->m_numMips = texDesc.MipLevels;
				hTexture->m_width = texDesc.Width;
				hTexture->m_height = texDesc.Height;
				hTexture->m_format = TypeConversion::ConvertBufferFormat(texDesc.Format);
				break;
			}
		default:
			{
				CR_ERROR_STRING("Unknown texture type");
			}
		}
	}

	void DeviceDirectX11::CreateStreamDescription(StreamDescriptionHandle hStreamDescription)
	{
		cr::ClearObject(hStreamDescription->m_internal);
		
		for (cr::size_t idxElement = 0; idxElement < hStreamDescription->m_numElements; ++idxElement)
		{
			StreamElement& srcElement = hStreamDescription->m_elements[idxElement];
			D3D11_INPUT_ELEMENT_DESC& dstElement = hStreamDescription->m_internal.m_elements[idxElement];
			dstElement.SemanticName = srcElement.m_name;
			dstElement.SemanticIndex = 0;
			dstElement.Format = TypeConversion::ConvertType(srcElement.m_hsType);
			dstElement.InputSlot = 0;
			dstElement.AlignedByteOffset = srcElement.m_offset;
			dstElement.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
			dstElement.InstanceDataStepRate = 0;
		}
	}

	void DeviceDirectX11::CreateRenderBinding(RenderBindingHandle hRenderBinding)
	{
		cr::ClearObject(hRenderBinding->m_internal);

		D3D11_INPUT_ELEMENT_DESC desc[Constants::MaxStreamElements];
		memset(desc, 0, sizeof(D3D11_INPUT_ELEMENT_DESC)*Constants::MaxStreamElements);
		UINT numDescElements = 0;
		for (cr::size_t idxVertexStream = 0; idxVertexStream < Constants::MaxVertexStreamBindings; ++idxVertexStream)
		{
			BufferHandle hVertexBuffer = hRenderBinding->m_vertexBuffer[idxVertexStream];
			if (hVertexBuffer.IsValid())
			{
				StreamDescriptionHandle hStreamDesc = hVertexBuffer ->m_hStreamDescription;
				memcpy(&desc[numDescElements], hStreamDesc->m_internal.m_elements, sizeof(D3D11_INPUT_ELEMENT_DESC)*hStreamDesc->m_numElements);
				numDescElements += hStreamDesc->m_numElements;
				hRenderBinding->m_internal.m_numVertexBuffers++;
				hRenderBinding->m_internal.m_pVertexBuffers[idxVertexStream] = hVertexBuffer->m_internal.m_pBuffer;
				hRenderBinding->m_internal.m_pStrides[idxVertexStream] = hVertexBuffer->m_stride;
				hRenderBinding->m_internal.m_pOffsets[idxVertexStream] = 0;
			}
		}

		m_pDevice->CreateInputLayout(
			desc,
			numDescElements, 
			hRenderBinding->m_hGpuProgram->m_internal.m_pVertexShaderCode, 
			hRenderBinding->m_hGpuProgram->m_internal.m_vertexShaderCodeSize, 
			&hRenderBinding->m_internal.m_pInputLayout);
	}

	void DeviceDirectX11::CreateGpuProgram(GpuProgramHandle hGpuProgram)
	{
		cr::ClearObject(hGpuProgram->m_internal);
		
		for (cr::size_t i = 0; i < hGpuProgram->m_numPrograms; ++i)
		{
			DX11Helpers::DX11ShaderHeader* pHeader = (DX11Helpers::DX11ShaderHeader*)hGpuProgram->m_programs[i].m_pData;
			void* pShaderCode = (void*)(pHeader+1);
			
			switch (hGpuProgram->m_programs[i].m_type)
			{
			case prehash::hs_PixelShader:
				{
					HRESULT res = m_pDevice->CreatePixelShader(pShaderCode, pHeader->m_codeSize, NULL, &hGpuProgram->m_internal.m_pPixelShader);
					CR_ASSERT(SUCCEEDED(res));
					break;
				}
			case prehash::hs_VertexShader:
				{
					HRESULT res = m_pDevice->CreateVertexShader(pShaderCode, pHeader->m_codeSize, NULL, &hGpuProgram->m_internal.m_pVertexShader);
					CR_ASSERT(SUCCEEDED(res));

					// Keep a copy of the vertex shader for future input layout patching. Seems like a crap thing, but I'll come back to it later!
					hGpuProgram->m_internal.m_pVertexShaderCode = new cr::uint8_t[pHeader->m_codeSize];
					hGpuProgram->m_internal.m_vertexShaderCodeSize = pHeader->m_codeSize;
					memcpy(hGpuProgram->m_internal.m_pVertexShaderCode, pShaderCode, pHeader->m_codeSize);

					break;
				}
			case prehash::hs_GeometryShader:
				{
					HRESULT res = m_pDevice->CreateGeometryShader(pShaderCode, pHeader->m_codeSize, NULL, &hGpuProgram->m_internal.m_pGeometryShader);
					CR_ASSERT(SUCCEEDED(res));
					break;
				}
			case prehash::hs_HullShader:
				{
					HRESULT res = m_pDevice->CreateHullShader(pShaderCode, pHeader->m_codeSize, NULL, &hGpuProgram->m_internal.m_pHullShader);
					CR_ASSERT(SUCCEEDED(res));
					break;
				}
			case prehash::hs_ComputeShader:
				{
					HRESULT res = m_pDevice->CreateComputeShader(pShaderCode, pHeader->m_codeSize, NULL, &hGpuProgram->m_internal.m_pComputeShader);
					CR_ASSERT(SUCCEEDED(res));
					break;
				}
			}
		}
	}

	void DeviceDirectX11::CreateStateBlock(StateBlockHandle hStateBlock)
	{
	}

	void DeviceDirectX11::CreateConstantBuffer(ConstantBufferHandle hConstantBuffer)
	{
// 		cr::ClearObject(hConstantBuffer->m_internal);
// 
// 		bool isSamplers = (hConstantBuffer->m_target == ConstantBufferTarget::PixelSamplers) || (hConstantBuffer->m_target == ConstantBufferTarget::VertexSamplers);
// 
// 		if (isSamplers)
// 		{
// 			hConstantBuffer->m_internal.m_ppTextures = new ID3D11ShaderResourceView*[hConstantBuffer->m_numSlots];
// 		}
// 		else
// 		{
// 			hConstantBuffer->m_internal.m_buffer.m_format = BufferFormat::ConstantBuffer;
// 			hConstantBuffer->m_internal.m_buffer.m_hStreamDescription = NULL;
// 			hConstantBuffer->m_internal.m_buffer.m_pInitData = 0;
// 			hConstantBuffer->m_internal.m_buffer.m_stride = 16;
// 			hConstantBuffer->m_internal.m_buffer.m_numElements = hConstantBuffer->m_numSlots;
// 			CreateBuffer(hConstantBuffer->m_internal.m_buffer);
// 
// 			hConstantBuffer->m_internal.m_pRaw = _aligned_malloc(16 * hConstantBuffer->m_numSlots, 16);
// 		}
	}

	// Object destruction
	void DeviceDirectX11::ReleaseContext(ContextHandle hContext)
	{
		hContext->m_internal.m_pContext->Release();
		cr::ClearObject(hContext->m_internal);
	}

	void DeviceDirectX11::ReleaseBuffer(BufferHandle hBuffer)
	{
		hBuffer->m_internal.m_pBuffer->Release();
		cr::ClearObject(hBuffer->m_internal);
	}

	void DeviceDirectX11::ReleaseRenderTarget(RenderTargetHandle hRenderTarget)
	{
	}

	void DeviceDirectX11::ReleaseRenderSurface(RenderSurfaceHandle hRenderSurface)
	{
		if (BufferFormat::IsDepthFormat(hRenderSurface->m_format))
		{
			hRenderSurface->m_internal.m_pDepthStencilView->Release();
		}
		else
		{
			hRenderSurface->m_internal.m_pRenderTargetView->Release();
		}
		cr::ClearObject(hRenderSurface->m_internal);
	}

	void DeviceDirectX11::ReleaseTexture(TextureHandle hTexture)
	{
	}

	void DeviceDirectX11::ReleaseStreamDescription(StreamDescriptionHandle hStreamDescription)
	{
	}

	void DeviceDirectX11::ReleaseRenderBinding(RenderBindingHandle /*hRenderBinding*/)
	{
	}

	void DeviceDirectX11::ReleaseGpuProgram(GpuProgramHandle hGpuProgram)
	{
		if (hGpuProgram->m_internal.m_pVertexShader)
			hGpuProgram->m_internal.m_pVertexShader->Release();

		if (hGpuProgram->m_internal.m_pPixelShader)
			hGpuProgram->m_internal.m_pPixelShader->Release();

		if (hGpuProgram->m_internal.m_pGeometryShader)
			hGpuProgram->m_internal.m_pGeometryShader->Release();

		if (hGpuProgram->m_internal.m_pHullShader)
			hGpuProgram->m_internal.m_pHullShader->Release();

		if (hGpuProgram->m_internal.m_pComputeShader)
			hGpuProgram->m_internal.m_pComputeShader->Release();

		cr::ClearObject(hGpuProgram->m_internal);
	}

	void DeviceDirectX11::ReleaseStateBlock(StateBlockHandle hStateBlock)
	{
	}

	void DeviceDirectX11::ReleaseConstantBuffer(ConstantBufferHandle hConstantBuffer)
	{
		cr::ClearObject(hConstantBuffer->m_internal);
	}
}



#endif // GK_DEVICE_DIRECTX11